package org.springblade.metadata.app;

import static org.springblade.core.cache.constant.CacheConstant.MENU_CACHE;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.apache.atlas.AtlasServiceException;
import org.apache.atlas.model.SearchFilter;
import org.apache.atlas.model.TypeCategory;
import org.apache.atlas.model.typedef.AtlasEntityDef;
import org.apache.atlas.model.typedef.AtlasTypesDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasConstraintDef;
import org.apache.commons.lang3.StringUtils;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import org.springblade.metadata.entity.Category;
import org.springblade.metadata.entity.MetadataVersion;
import org.springblade.metadata.entity.UpgradeContent;
import org.springblade.metadata.entity.UpgradeContent.Content;
import org.springblade.metadata.service.ICategoryService;
import org.springblade.metadata.service.IMetadataVersionService;
import org.springblade.metadata.service.TypeSystemService;
import org.springblade.metadata.utils.FilterUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.google.gson.Gson;

import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springblade.metadata.entity.UpgradeContent.Type;
@RestController
@RequestMapping("v2/type")
@AllArgsConstructor
@Slf4j
public class TypesystemController {
	
    @RequestMapping(value="*",method=RequestMethod.GET)
    String index(HttpServletRequest request){
    	String path  = request.getContextPath();
    	log.info(path);
        return "type index";
    }
    private TypeSystemService  typeSystemService;
    
    private final ICategoryService categoryService;
    
    @GetMapping("/{guid}/info")
    @ApiOperationSupport(order = 42)
    R<AtlasEntityDef>  getEntityDef(@PathVariable String guid){
    	AtlasEntityDef e = typeSystemService.getEntityByName(guid);
    	return R.data(e);
    }
    
    
    /**
  	 * /entityref/types
  	 */
  	@GetMapping("/entityref/types")
  	@ApiOperationSupport(order = 41)
  	@ApiOperation(value = "list metadata entity types", notes = "list metadata entity types")
  	public R getTypes() {
  		
  		return R.data(categoryService.availableCategory(null, "ENTITY"));
  	}
    
    /**
	 * 新增或修改
	 */
	@PostMapping("/entityref/submit")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "metadata entity add or  update", notes = "传入Category")
	public R submit(@Valid @RequestBody Category c) {
		if(c.getParentId()==null || c.getParentId() < 1) {
			return R.fail(ResultCode.PARAM_TYPE_ERROR);
		}
		Category  p = categoryService.getById(c.getParentId());
		if(null == p) {
			return R.fail(ResultCode.PARAM_TYPE_ERROR,"parent non-existent");
		}
		
		if(StringUtils.isBlank(c.getCode())) {
			return R.fail(ResultCode.PARAM_MISS,"code");
		}
		Category  cc = categoryService.getBydCode(c.getCode());
		if(cc !=null ) {
			if(c.getId()== null || c.getId().longValue() != cc.getId().longValue()) {
				return R.fail(ResultCode.PARAM_VALID_ERROR,"code exist");
			}
		}
			AtlasEntityDef  e = buildEntity(c,p);
			c.setCategory(e.getServiceType());
			c.setTypeCategory(TypeCategory.ENTITY.name()
					);
			c.setSource(FilterUtil.gosn.toJson(e));
//				if(c.getId() == null || c.getId() <1 ) {
////					e = typeSystemService.createEntityType(e);
////					c.setOutCode(e.getGuid());
//				}
				if (categoryService.submit(c)) {
					CacheUtil.clear(MENU_CACHE);
					CacheUtil.clear(MENU_CACHE, Boolean.FALSE);
					// 返回懒加载树更新节点所需字段
					Kv kv = Kv.create().set("id", String.valueOf(c.getId()));
					return R.data(kv);
				}
//			} catch (AtlasServiceException e1) {
//				// TODO Auto-generated catch block
//				e1.printStackTrace();
//				return R.fail("操作失败");
//			}
		
		return R.fail("操作失败");
	}
	
	private IMetadataVersionService  metadataVersionService;
	
	
	@PostMapping("/entityref/attrlocal/{id}")
	@ApiOperationSupport(order = 2)
	@ApiOperation(value = "entityref  attr update local", notes = "传入AtlasEntityDef.")
	R updateEntityRefAttrLocal(@PathVariable Long id,@Valid @RequestBody AtlasEntityDef def) {
//		if(StringUtils.isBlank(def.getGuid())) {
//			return R.fail(ResultCode.PARAM_MISS,"guid");
//		}
		
		Category  c = categoryService.getById(id);
		
		if(c == null ) {
			return R.fail(ResultCode.PARAM_MISS,"id");
		}
		
			def.getAttributeDefs().forEach(attr->{
				attr.setCardinality(AtlasAttributeDef.Cardinality.SINGLE);
				attr.setValuesMinCount(0);
				attr.setValuesMaxCount(1);
				attr.setIncludeInNotification(false);
				attr.setConstraints(Collections.<AtlasConstraintDef>emptyList());
			});
			
			AtlasEntityDef  a  = FilterUtil.gosn.fromJson(c.getSource(), AtlasEntityDef.class);
			a.setAttributeDefs(def.getAttributeDefs());
			c.setSource(FilterUtil.gosn.toJson(a));
			
			categoryService.saveOrUpdate(c);
				
			return R.success("ok");
		
	}
	
	@PostMapping("/entityref/attr")
	@ApiOperationSupport(order = 6)
	@ApiOperation(value = "entityref  attr update", notes = "传入AtlasEntityDef.")
	R updateEntityRefAttr(@Valid @RequestBody AtlasEntityDef def) {
		if(StringUtils.isBlank(def.getGuid())) {
			return R.fail(ResultCode.PARAM_MISS,"guid");
		}
		try {
			def.getAttributeDefs().forEach(attr->{
				attr.setCardinality(AtlasAttributeDef.Cardinality.SINGLE);
				attr.setValuesMinCount(0);
				attr.setValuesMaxCount(1);
				attr.setIncludeInNotification(false);
				attr.setConstraints(Collections.<AtlasConstraintDef>emptyList());
			});
			AtlasEntityDef  o  = typeSystemService.getEntityByGuid(def.getGuid());
			
			MetadataVersion  mv = new MetadataVersion();
			mv.setMetadataId(o.getGuid());
			mv.setContentJson(new Gson().toJson(o));
			metadataVersionService.addMetadataVersion(mv);
			
			o.setAttributeDefs(def.getAttributeDefs());
			typeSystemService.update(o);
			return R.success("ok");
		} catch (AtlasServiceException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return R.fail(ResultCode.PARAM_MISS,"guid wrong");
		}
		
	}
	
	@PostMapping("/entityref/pub")
	@ApiOperationSupport(order = 3)
	@ApiOperation(value = "entityref  public ", notes = "传入"
			+ "ids.")
	public R pub(@Valid @RequestBody List<Long> ids) {
		if(!ids.isEmpty()) {
			for(Long id :ids) {
				Category c  = categoryService.getById(id);
				
				AtlasEntityDef  ae = FilterUtil.gosn.fromJson(c.getSource(), AtlasEntityDef.class) ;
				
				try {
					AtlasEntityDef  n = typeSystemService.createEntityType(ae);
					
					c .setOutCode(n.getGuid());
					
					categoryService.saveOrUpdate(c);
					
					MetadataVersion  mv = new MetadataVersion();
					mv.setMetadataId(c.getId().toString());
					mv.setContentJson(new Gson().toJson(n));
					
					MetadataVersion la = metadataVersionService.latestMeta(mv);
					if(la !=null) {
						AtlasEntityDef o = FilterUtil.gosn.fromJson(la.getContentJson(), AtlasEntityDef.class);
						mv.setUpJson(fetchModify(n, o));
						
					}
					metadataVersionService.addMetadataVersion(mv);
					
				} catch (AtlasServiceException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				return 	 R.fail(ResultCode.FAILURE,e.getMessage());
				}
			}
		}
		return R.success(ResultCode.SUCCESS);
	}
	String fetchModify(AtlasEntityDef n , AtlasEntityDef o) {
		List<AtlasAttributeDef>  nas = n.getAttributeDefs();
		List<AtlasAttributeDef>  oas = o.getAttributeDefs();
		
		Map<String, AtlasAttributeDef> nnames =  nas.stream().collect(Collectors.toMap(AtlasAttributeDef::getName, a ->a));
		
		Map<String, AtlasAttributeDef> onames =  oas.stream().collect(Collectors.toMap(AtlasAttributeDef::getName, a ->a));
		
		
		List<AtlasAttributeDef>  adds  = nas.stream().filter(a-> ! onames.containsKey(a.getName())).collect(Collectors.toList());
		
		List<AtlasAttributeDef>  removes  = oas.stream().filter(a-> ! nnames.containsKey(a.getName())).collect(Collectors.toList());
		
		List<AtlasAttributeDef>  unions  = oas.stream().filter(a-> nnames.containsKey(a.getName())).filter(a-> onames.containsKey(a.getName())).collect(Collectors.toList());
		
		UpgradeContent uc = new UpgradeContent();
		
		List<UpgradeContent.Content> attrs  =new ArrayList<>();
		
		//org.springblade.metadata.entity.UpgradeContent.Type
		adds.forEach(a ->{
			Content c = new   Content(Type.ADD, "{}", FilterUtil.gosn.toJson(a));
			attrs.add(c);
		});
		
		removes.forEach(a ->{
			Content c = new   Content(Type.DELETE, FilterUtil.gosn.toJson(a), "{}");
			attrs.add(c);
		});
		
		
		unions.forEach( oo ->{
			AtlasAttributeDef nn = nnames.get(oo.getName());
			if(!nn.equals(oo)) {
				Content c = new   Content(Type.MODIFY, FilterUtil.gosn.toJson(oo),  FilterUtil.gosn.toJson(nn));
				attrs.add(c);
			}
		});
		
		uc.setAttrs(attrs);
		return FilterUtil.gosn.toJson(uc);
	}
	@PostMapping("/entityref/dismiss")
	@ApiOperationSupport(order = 4)
	@ApiOperation(value = "entityref  dismiss ", notes = "传入"
			+ "ids.")
	public R dismiss(@Valid @RequestBody List<Long> idls) {
		if(!idls.isEmpty()) {
			for(Long idl : idls) {
				Category  c = categoryService.getById(idl);
				if(c==null) {
					return R.fail(ResultCode.PARAM_VALID_ERROR);
				}
				if(c.getParentId()<1) {
					return R.fail(ResultCode.PARAM_VALID_ERROR);
				}
				try {
					typeSystemService.removeEntityTypeByName(c.getCode());
				} catch (AtlasServiceException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					throw new ServiceException(ResultCode.INTERNAL_SERVER_ERROR);
				}finally {
					c.setOutCode("");
					CacheUtil.clear(MENU_CACHE);
					CacheUtil.clear(MENU_CACHE, Boolean.FALSE);
					categoryService.saveOrUpdate(c);
				}
			}
			return R.success(ResultCode.SUCCESS);
		}
		return R.fail(ResultCode.FAILURE);
	}
	/**
	 * 删除
	 */
	@PostMapping("/remove")
	@ApiOperationSupport(order = 5)
	@ApiOperation(value = "删除 entityref", notes = "传入ids")
	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
		List<Long> idls = Func.toLongList(ids);
		for(Long idl : idls) {
			Category  c = categoryService.getById(idl);
			if(c==null) {
				return R.fail(ResultCode.PARAM_VALID_ERROR);
			}
			if(c.getParentId()<1) {
				return R.fail(ResultCode.PARAM_VALID_ERROR);
			}
			if(StringUtils.isNotBlank(c.getOutCode())) {
				return R.fail(ResultCode.FAILURE);
			}
		}
		CacheUtil.clear(MENU_CACHE);
		CacheUtil.clear(MENU_CACHE, Boolean.FALSE);
		return R.status(categoryService.removeCategory(ids));
	}
    @PostMapping("/syn")
    R<String> syn(HttpServletRequest httpServletRequest){
    	SearchFilter searchFilter = getSearchFilter(httpServletRequest);
    	AtlasTypesDef  types = typeSystemService.getAll(searchFilter);
    	List<AtlasEntityDef> aeds = types.getEntityDefs();
//    	aeds.forEach(e->{
//    		log.info("name is {}  ; service type is : {}",e.getName(),e.getServiceType());
//    	});
    	Map<String,List<AtlasEntityDef>> groups = aeds.stream().collect(Collectors.groupingBy(aed ->{
    		if(null == aed.getServiceType()) {
    			return "none";
    		}
    		return aed.getServiceType();
    	}));
    	
    	groups.forEach((k,v)->{
    		log.info("service type is {}",k);
    		Category c = categoryService.getBydCode(k);
    		if(c ==  null) {
    			Category  category= buildByServiceType(k, "ENTITY");
    			
    			category.setIsOpen(2);
    			categoryService.save(category);
    			c = categoryService.getBydCode(category.getCode());
    		}else {
    			log.info("service type is {}  , exist" , k);
    		}
    		
    		final Category  p = c;
    		v.forEach(e->{
    			Category ae  = categoryService.getBydOutCode(e.getGuid());
    			if(null == ae) {
    				ae = buildEntity(e, p.getId());
    				categoryService.save(ae);
    			}else {
    				ae.setSource(FilterUtil.gosn.toJson(e));
    				categoryService.saveOrUpdate(ae);
    			}
    		});
    		log.info("Category  id  is {}  ,  code  is : {}" , c.getId(),c.getCode());
    		
    	});
    	return R.data("ok");
    }
    
    
    AtlasEntityDef buildEntity(Category  c,Category p) {
    	String name = c.getCode();
    	String des  = c.getRemark();
    	String v  ="1";
    	AtlasEntityDef  e  = new AtlasEntityDef(name, des , v);
    	e.setServiceType(p.getCode());
    	e.setSuperTypes(Collections.singleton("DataSet"));
    	return e;
    }
    
    Category buildEntity(AtlasEntityDef  e,Long parentId) {
    	Category c = new Category();
    	
    	c.setParentId(parentId);
    	
    	c.setCode(e.getName());
    	
    	c.setOutCode(e.getGuid());
    	
    	c.setAlias(e.getName());
    	
    	c.setName(e.getName());
    	
    	c.setRemark(e.getDescription());
    	
    	c.setCategory(e.getServiceType());
    	
    	c.setTypeCategory(TypeCategory.ENTITY.name());
    	c.setTypeVersion(e.getVersion().toString());
    	
    	c.setSource(FilterUtil.gosn.toJson(e));
    	
    	return c;
    }
    
    Category buildByServiceType(String serviceType,String category) {
    	Category menu = new Category();
    	menu.setParentId(0l);
    	menu.setName(serviceType);
    	menu.setAlias(serviceType);
    	menu.setCode(serviceType);
    	if("none".equals(serviceType)) {
    		menu.setOutCode(null);
    	}else {
    		menu.setOutCode(serviceType);
    	}
    	menu.setCategory(category);
    	return menu;
    }
    
    @GetMapping("/all")
    R<AtlasTypesDef> types(HttpServletRequest httpServletRequest) {
    	SearchFilter searchFilter = getSearchFilter(httpServletRequest);
    	AtlasTypesDef  types = typeSystemService.getAll(searchFilter);
    	return R.data(types);
    }
    
    @GetMapping("/entitydef/name/{name}")
    public R<AtlasEntityDef> getEntityDefByName(@PathVariable String name)  {

        AtlasEntityDef ret = typeSystemService.getEntityByName(name);

        return R.data(ret);
    }
    /**
     * Populate a SearchFilter on the basis of the Query Parameters
     * @return
     */
    private SearchFilter getSearchFilter(HttpServletRequest httpServletRequest) {
        SearchFilter ret    = new SearchFilter();
        Set<String>  keySet = httpServletRequest.getParameterMap().keySet();

        for (String k : keySet) {
            String key   = String.valueOf(k);
            String value = String.valueOf(httpServletRequest.getParameter(k));

            if (key.equalsIgnoreCase("excludeInternalTypesAndReferences") && value.equalsIgnoreCase("true")) {
                FilterUtil.addParamsToHideInternalType(ret);
            } else {
                ret.setParam(key, value);
            }
        }

        return ret;
    }
}